﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

using System.CodeDom;
using System.CodeDom.Compiler;

using MartinCarolan.EventSpy.UI;
using System.Threading;
using System.Windows;
using System.Collections.ObjectModel;
using System.ComponentModel;
using DrWPF.Windows.Data;
using System.Reflection.Emit;

namespace MartinCarolan.EventSpy
{
    public class EventSpy : IEventListener
    {
        private static object _lock = new object();
        private static int _relativeTime = 0;

        private static ObservableDictionary<object, ObservableCollection<EventRegistrationEntry>> _registry = new ObservableDictionary<object, ObservableCollection<EventRegistrationEntry>>();
        private static ObservableCollection<Event> _events = new ObservableCollection<Event>();

        public static ObservableDictionary<object, ObservableCollection<EventRegistrationEntry>> Registry
        {
            get { return _registry; }
        }

        public static ObservableCollection<Event> Events
        {
            get { return _events; }
        }

        private ManualResetEvent resetEvent = new ManualResetEvent(false);

        private static Window mainWindow;

        public EventSpy()
        {
            lock (_lock)
            {
                var uiThread = new Thread(UIThread);
                uiThread.SetApartmentState(ApartmentState.STA);
                uiThread.IsBackground = true;
                uiThread.Start();
            }
        }

        public void Unregister(object o)
        {
            lock (_lock)
            {
                resetEvent.WaitOne();
                if (Registry.ContainsKey(o))
                {
                    foreach (var registration in Registry[o])
                    {
                        registration.Event.RemoveEventHandler(o, registration.Delegate);
                    }

                    mainWindow.Dispatcher.Invoke(new Action(
                        delegate
                        {
                            Registry.Remove(o);
                        }));
                }
            }
        }

        public void Register(object o)
        {
            lock (_lock)
            {
                resetEvent.WaitOne();
                var compiler = new HandlerCompiler(this);

                foreach (var eventInfo in o.GetType().GetEvents())
                {
                    compiler.AddEvent(eventInfo);
                }

                object handler = compiler.CompileHandler(o);

                foreach (var eventInfo in o.GetType().GetEvents())
                {
                    Delegate d = Delegate.CreateDelegate(eventInfo.EventHandlerType, handler, compiler.GetHandlerMethodName(eventInfo));
                    eventInfo.AddEventHandler(o, d);

                    if (!Registry.ContainsKey(o))
                    {
                        mainWindow.Dispatcher.Invoke(new Action(
                            delegate
                            {
                                Registry.Add(o, new ObservableCollection<EventRegistrationEntry>());
                            }));
                    }

                    mainWindow.Dispatcher.Invoke(new Action(
                        delegate
                        {
                            Registry[o].Add(new EventRegistrationEntry() { Delegate = d, Event = eventInfo });
                        }));
                }
            }
        }

        public void EventRaised(object obj, string name, object[] arguments)
        {
            lock (_lock)
            {
                mainWindow.Dispatcher.Invoke(new Action(
                    delegate
                    {
                        var argumentNames = Registry[obj].FirstOrDefault(e => e.Event.Name == name).Event.EventHandlerType.GetMethod("Invoke").GetParameters();

                        var aname = new AssemblyName("EventSpyArguments");
                        var abuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.RunAndSave);
                        var mbuilder = abuilder.DefineDynamicModule(aname.Name, aname.Name + ".dll");
                        var builder = mbuilder.DefineType("DynType", TypeAttributes.Public);

                        foreach (var argument in argumentNames)
                        {
                            builder.DefineField(argument.Name, argument.ParameterType, FieldAttributes.Public);
                        }

                        Type argumentType = builder.CreateType();
                        object argumentInstance = Activator.CreateInstance(argumentType);

                        for (int i = 0; i < argumentNames.Length; ++i)
                        {
                            argumentType.GetField(argumentNames[i].Name).SetValue(argumentInstance, arguments[i]);
                        }

                        Events.Add(new Event() { ArgumentWrapper = argumentInstance, Arguments = arguments, Name = name, Source = obj, TimeStamp = DateTime.Now, RelativeTime = ++_relativeTime });
                    }));
            }
        }

        private void UIThread()
        {
            mainWindow = new MainWindow(this);
            resetEvent.Set();
            mainWindow.ShowDialog();
        }
    }
}
